/****************************************************************************
 * ifu.v
 ****************************************************************************/
`include "defines.v"

/**
 * Module: ifu
 * 
 * 所有信号遵从正逻辑，即信号电平为1的含义与信号变量名称的含义一致，如rst信号
 * 为1代表复位，0代表复位信号无效，系统正常工作
 *
 * 取指令单元，检测后端译码和执行单元是否可用，可用的情况下每个时钟周期从存储
 * 器中取出一个指令，并经过初步译码后传递给后面的译码和执行单元执行

   取指模块包含四组接口：
    1. 对接系统，接受系统的复位和启动
    2. 对接代码存储器，读取指令
    3. 对接后端执行单元exu，根据exu执行结果信号阻塞取指、或生成取指地址、复位系统
    4. 对接后端译码单元dec，将指令的基本信息传递给译码进行深度解析
 */

module ifu
#(	
    parameter   DATA_WIDTH  =   16,
    parameter   ADDR_WIDTH  =   12,
    parameter   DELAY_TIME  =   3
)
(
    input                           rst,                    // 系统复位信号，同步复位，高电平有效
    input                           clk,                    // 系统工作时钟

    // 上层系统向ifu发送启动工作的信号接口，icb标准信号，ifu为从设备接收命令
    input                           sys2ifu_cmd_start,      // 系统工作启动信号，高电平有效
    input                           sys2ifu_cmd_req,
    output                          sys2ifu_cmd_ack,

    // 程序存储器相关接口，采用icb标准信号接口，ifu作为主设备读写mem
    output                          ifu2mem_cmd_wr,         // 写读命令，高电平写，低电平读
    output                          ifu2mem_cmd_req,      
    input                           ifu2mem_cmd_ack,      
    input                           ifu2mem_rsp_req,
    output                          ifu2mem_rsp_ack,
    input                           ifu2mem_rsp_err,
    output     [ADDR_WIDTH-1 : 0]   ifu2mem_addr,           // 地址
    output     [DATA_WIDTH-1 : 0]   ifu2mem_dout,           // 写数据
    input      [DATA_WIDTH-1 : 0]   ifu2mem_din,            // 读数据

    // 后端译码和执行单元相关接口，此时取指模块作为主设备向译码执行单元写数据
    output                          ifu2exu_cmd_req,        // 发送给后端译码和执行单元的命令状态信号，高电平有效
    input                           ifu2exu_cmd_ack,        // 后端译码执行单元反馈的命令接受状态信号，高电平有效
    input                           ifu2exu_rsp_req,        // 后端执行单元exu反馈的请求信号
    output                          ifu2exu_rsp_ack,        // ifu发送给后端exu的请求接受状态信号
    input                           ifu2exu_rsp_done,       // exu向ifu发送的程序执行完毕信号，HLT指令执行完
    input                           ifu2exu_rsp_taken,      // exu根据执行结果反馈给ifu的跳转指令执行结果，1-跳转
    input                           ifu2exu_rsp_mem_addr,   // exu根据执行结果反馈给ifu的跳转目的地址

    // ifu和dec模块的接口信号，因为dec模块为纯组合逻辑，不涉及时序控制，因此无请求和响应交互信号
    output                          ifu2dec_instr_jmp,      // 无条件跳转指令
    output                          ifu2dec_instr_bjp,      // 条件跳转指令
    output     [DATA_WIDTH-1 : 0]   ifu2dec_instr,          // 指令
    output     [ADDR_WIDTH-1 : 0]   ifu2dec_pc              // 当前指令的pc值
);
	

    // 模块复位信号，外部系统rst或者deu执行完成信号
    wire ifetch_rst = rst | ifu2exu_rsp_done;


    // 工作状态生成，外部发送启动脉冲后，在模块当前空闲的状态下才会置位工作状态
    // 一旦取指模块开始工作，置位系统命令反馈信号ack
    wire ifetch_working;
    dfflr #(1) ifetch_working_inst
    (
        .rst(ifetch_rst), 
        .clk(clk), 
        .en(~ifetch_working), 
        .d(sys2ifu_cmd_start & sys2ifu_cmd_req), 
        .q(ifetch_working)
    );
    assign sys2ifu_cmd_ack = ifetch_working;


    // 当ifu的工作状态置位后，系统开始从mem中地址为0的位置开始取指令
    /* 
        这里的ifu2exu_cmd_ack信号需要详细解释下：
        因为此处理器设计为简单起见，不采用分支预测机制来提高性能，而且采用的是顺序发射，顺序执行，顺序写回的机制，因此不会预取指令，也不用设计流水线冲刷功能。
        为保证高效执行，正常情况下，ifu模块在上一条指令执行的过程中，下一条指令就已经取出。但是有三个特殊情况：
        1. 遇到条件跳转指令，就要求前序指令执行完成后才能生成下一条指令地址
        2. 遇到长周期指令，就要求ifu模块阻塞一段时间，等待当前指令被exu取走后才能生成下一条指令地址
        3. 遇到无条件跳转指令，因为很容易计算出来下一跳指令的地址，但是无条件跳转因为涉及到保存程序现场，读写内存，执行时间较长，也要特殊处理，可以与长周期指令一样处理
    */
    wire [ADDR_WIDTH-1 : 0] mem_addr; 
    dfflr #(ADDR_WIDTH) mem_addr_inst
    (
        .rst(ifetch_rst),
        .clk(clk), 
        .en(ifetch_working & ifu2exu_cmd_ack),  // 跳转指令和长周期指令exu会通过命令接收状态cmd_ack信号阻塞取指
        .d(mem_addr),
        .q(ifu2mem_addr)
    );
    // 此处icb的信号并没有全部使用，因为对接的ram为sram，如果对接系统总线需要扩展实现所有信号
    assign ifu2mem_cmd_req                  = ifetch_working;
    assign ifu2mem_cmd_wr                   = 1'b0;                 // 一直读，如果用~ifu2mem_cmd_req信号，容易产生竞争冒险
    wire [DATA_WIDTH-1 : 0] ifu_instr_all   = ifu2mem_din;          // 读取内存输出指令数据
    

    // 对指令初步译码，这里仅译码出对取指地址生成会产生影响的指令，完整的译码功能均放在译码模块实现
    wire sub_op1_00 = ifu_instr_all[15:14] == 2'b00;
    wire sub_op1_01 = ifu_instr_all[15:14] == 2'b01;

    assign ifu2dec_instr_jmp   = sub_op1_00 & ifu_instr_all[13];
    assign ifu2dec_instr_bjp   = sub_op1_01;

    assign ifu2dec_pc          = ifu2mem_addr;
    assign ifu2exu_cmd_req     = ifetch_working;
    assign ifu2dec_instr       = ifu_instr_all;


    // 根据指令信号生成下一个指令的内存地址，这部分要考虑不同指令的情况
    // 跳转指令需要等待上一条指令完成后根据执行结果计算下一条指令地址
    // 顺序指令可以直接读取下一条指令即可
    assign mem_addr = (ifu2dec_instr_jmp | (ifu2dec_instr_bjp & ifu2exu_rsp_taken)) ? ifu2exu_rsp_mem_addr : ifu2mem_addr + 1;


endmodule
